from mpd import MPDClient, MPDError, ConnectionError
import socket


class StateMachine(object):
    def __init__(self):
        super(StateMachine,self).__init__()
        self.state = None
        self.states = dict()

    def transition(self, state, *args, **kwargs):
        if not isinstance(state, State):
            state = self.states[state]
        if self.state:
            self.state.leave(*args, **kwargs)
        print "Transition from ", self.state, " to ", state
        self.state = state
        self.state.enter(*args, **kwargs)

    def add_state(self, name, state):
        self.states[name] = state

    def state(self, name):
        return self.states[name]


class State(object):
    def __init__(self, name, machine):
        super(State,self).__init__()
        self.name = name
        self.machine = machine
        if self.machine:
            self.machine.add_state(name, self)

    def enter(self, *args, **kwargs):
        pass

    def leave(self, *args, **kwargs):
        pass

    def transition(self, state, *args, **kwargs):
        self.machine.transition(state, *args, **kwargs)


class MPDStateMachine(StateMachine):
    def __init__(self):
        super(MPDStateMachine,self).__init__()
        self.globals = MPDGlobals()
        self.globals.commands = MPDClient()._commands.keys()
        self.initial_state = Initial("initial", self)
        self.disconnected_state = Disconnected("disconnected", self)
        self.connected_state = Connected("connected", self)
        self.retrying_state = Retrying("retrying", self)
        self.transition(self.initial_state)


class MPDGlobals(object):
    def __init__(self):
        super(MPDGlobals,self).__init__()
        self.client = None
        self.server = None
        self.port = None
        self.last_error = None

class MPDState(State):
    def __init__(self, name, machine):
        super(MPDState,self).__init__(name, machine)

    def globals(self):
        return self.machine.globals

    def connect(self):
        raise ArgumentError()

    def disconnect(self):
        raise ArgumentError()

    def has_command(self, command):
        if self.globals().client:
            return self.globals().client._commands.has_key(command)
        else:
            return False

    def command(self, command):
        try:
            raise AttributeError()
        except AttributeError as e:
            self.globals().last_error = e
            raise e

    def set_server(self, server, port=6600, password=None):
        self.globals().server = server
        self.globals().port = port
        self.globals().password = password
        self.transition("disconnected")



class Disconnected(MPDState):
    def enter(self, *args, **kwargs):
        try:
            self.globals().client.disconnect()
        except:
            pass
        self.globals().client = None

    def connect(self):
        if self.globals().server and self.globals().port:
            self.globals().client = MPDClient()
            try:
                self.globals().client.connect(self.globals().server,
                                              self.globals().port)
                self.globals().client.password(self.globals().password)
                self.globals().client.ping()
                self.transition("connected")
            except Exception as e:
                self.globals().last_error = e
                self.transition("disconnected")

    def disconnect(self):
        pass

    def command(self, command):
        self.last_error = None
        self.connect()
        if self.machine.state is not self:
            return self.machine.state.command(command)
        else:
            return None


class Connected(MPDState):
    def connect(self):
        pass

    def disconnect(self):
        self.transition("disconnected")

    def command(self, command):
        try:
            self.last_error = None
            return command(self.globals().client)
        except ConnectionError as e:
            self.globals().last_error = e
            self.transition("retrying")
            return self.machine.state.command(command)
        except socket.error as e:
            self.globals().last_error = e
            self.transition("disconnected")
        except MPDError as e:
            self.globals().last_error = e
            self.transition("disconnected")
        return None


class Retrying(Disconnected):
    def enter(self, *args, **kwargs):
        super(Retrying,self).enter(*args,**kwargs)
        self.connect()

    def command(self, command):
        try:
            self.last_error = None
            return command(self.globals().client)
        except MPDError as e:
            self.transition("disconnected")
        return None


class Initial(MPDState):
    pass



class MPD(object):
    def __init__(self):
        super(MPD,self).__init__()
        self.client = MPDStateMachine()
        self.mpd_commands = dict((k,k) for k in MPDClient()._commands)

    def __getattr__(self, attr):
        # print "getattr: " + attr
        if hasattr(self.client, attr):
            return getattr(self.client,attr)
        if hasattr(self.client.state, attr):
            return getattr(self.client.state,attr)
        if self.mpd_commands.has_key(attr):
            return lambda *args,**kwargs: self.client.state.command(lambda c: getattr(c,attr)(*args,**kwargs))
        else:
            raise AttributeError, attr



class Singleton(object):
    __instance = {}

    def __new__(cls, *args, **kwargs):
        newcls = args[0]
        args = args[1:]
        if not cls.__instance.get(newcls,None):
            cls.__instance[newcls] = newcls(*args, **kwargs)
        return cls.__instance[newcls]

    def instance():
        if hasattr(MPD,'__instance') and MPD.__instance:
            return MPD.__instance
        else:
            MPD.__instance = MPD()
            return MPD.__instance

